/********************************************************
 * 程序名称：实现水波纹显示								*
 * 编译环境：Visual C++ 6.0 / 2010，EasyX_20210730		*
 * 作　　者：豪 (QQ: 415051674)							*
 * 核心算法：参考网上算法									*
 * 发布日期：2013/3/20									*
 ********************************************************/


#include "easyc.h"  
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

#define PIC_WIDTH  800
#define PIC_HEIGHT 600
#define FPS_COUNT  8

#define true 1
#define false 0

void FrameFun();									// 帧逻辑函数，处理每一帧的逻辑
void nextFrame();
void RenderFun();									// 帧渲染函数，输出每一帧到显示设备

__IMAGE src_img;										// 原位图		
__IMAGE dest_img;				// 处理后显示的位图
DWORD* img_ptr1;									// 原图片片内存指针
DWORD* img_ptr2;									// 处理后显示的位图内存指针
int    stop_flag = 0;								// 停止标志


FILE* __imp___iob_func(void) {
    return _iob;
}

// 以下两个 buf 为每一个点的波幅，前者为当前波幅，后者为下一个时刻的波幅。
short* buf;
short* buf2;

void init() {
    src_img = imageInit(PIC_WIDTH, PIC_HEIGHT);
    dest_img = imageInit(PIC_WIDTH, PIC_HEIGHT);
    imageCarry(src_img, "./src/water.jpg", PIC_WIDTH, PIC_HEIGHT, false);
    buf = malloc((PIC_HEIGHT * PIC_WIDTH + PIC_WIDTH) * sizeof(short));
    buf2 = malloc((PIC_HEIGHT * PIC_WIDTH + PIC_WIDTH) * sizeof(short));
    graphCreate(PIC_WIDTH, PIC_HEIGHT);	// 初始化图形窗口
}


void main() {
    // 初始化设备，加载图片
    init();
    batchDrawBegin();					// 双缓冲
    bkModeSet(TRANSPARENT);	// 设置背景透明

    // 获得内存指针
    img_ptr1 = imageGetBuffer(src_img);
    img_ptr2 = imageGetBuffer(dest_img);

    // 初始化波幅数组
    memset(buf, 0, (PIC_HEIGHT * PIC_WIDTH + PIC_WIDTH) * sizeof(short));
    memset(buf2, 0, (PIC_HEIGHT * PIC_WIDTH + PIC_WIDTH) * sizeof(short));

    // Let's Go!
    while(stop_flag < 1200) {
        FrameFun();
        nextFrame();
        RenderFun();
        batchDrawFlush();	// 刷新双缓冲
        Sleep(1);
        stop_flag++;
    }
    batchDrawEnd();	// 结束双缓冲
    imageFree(src_img);
    imageFree(dest_img);
    free(buf);
    free(buf2);
    graphClose();	// 关闭图形窗口
    exit(0);		// 退出程序
}

// 计算出下一个时刻所有点的波幅
void nextFrame() {
    for(int i = PIC_WIDTH; i < PIC_HEIGHT * (PIC_WIDTH - 1); i++) {
        // 公式：X0'= (X1+X2+X3+X4) / 2 - X0
        buf2[i] = ((buf[i - PIC_WIDTH] + buf[i + PIC_WIDTH] + buf[i - 1] + buf[i + 1]) >> 1) - buf2[i];

        // 波能衰减
        buf2[i] -= buf2[i] >> 5;
    }

    short* ptmp = buf;
    buf = buf2;
    buf2 = ptmp;
}

// 处理当前时刻波幅影响之后的位图，保存在 dest_img 中
void RenderRipple() {
    int i = 0;
    for(int y = 0; y < PIC_HEIGHT; y++) {
        for(int x = 0; x < PIC_WIDTH; x++) {
            short data = 1024 - buf[i];

            // 偏移
            int a = ((x - PIC_WIDTH / 2) * data / 1024) + PIC_WIDTH / 2;
            int b = ((y - PIC_HEIGHT / 2) * data / 1024) + PIC_HEIGHT / 2;

            // 边界处理
            if(a >= PIC_WIDTH)		a = PIC_WIDTH - 1;
            if(a < 0)				a = 0;
            if(b >= PIC_HEIGHT)	b = PIC_HEIGHT - 1;
            if(b < 0)				b = 0;

            // 处理偏移 
            img_ptr2[i] = img_ptr1[a + (b * PIC_WIDTH)];
            i++;
        }
    }
}

// 鼠标模拟投石头
// 参数说明：
// (x, y): 鼠标坐标
// stonesize: “石头”的大小
// stoneweight: 投“石头”的力度
// Ps: 如果产生错误，一般就是数组越界所致，请酌情调整“石头”的大小和“石头”的力度
void disturb(int x, int y, int stonesize, int stoneweight) {
    // 突破边界不处理
    if((x >= PIC_WIDTH - stonesize) ||
        (x < stonesize) ||
        (y >= PIC_HEIGHT - stonesize) ||
        (y < stonesize))
        return;

    for(int posx = max(x - stonesize, 0); posx < min(x + stonesize, PIC_WIDTH); posx++) {
        for(int posy = max(y - stonesize, 0); posy < min(y + stonesize, PIC_HEIGHT); posy++) {
            if((posx - x) * (posx - x) + (posy - y) * (posy - y) < stonesize * stonesize) {
                buf[PIC_WIDTH * posy + posx] += stoneweight;
            }
        }
    }
}

// 计算fps
float getFps() {
    static int i = 0;
    static int oldTime = -114514;
    if(oldTime == -114514)
        oldTime = GetTickCount();
    static float fps;

    if(i > FPS_COUNT) {
        i = 0;
        int newTime = GetTickCount();
        int elapsedTime = newTime - oldTime;
        fps = FPS_COUNT / (elapsedTime / 1000.0f);
        oldTime = newTime;
    }
    i++;
    return fps;
}

// 渲染
void RenderFun() {
    RenderRipple();
    imagePut(0, 0, dest_img, SRCCOPY); // 使用位图的方式输出

    strPut(0, 0, "Wave - Left click and move mouse to create ripples\n Right click to exit");
    char s[20];
    sprintf(s, "FPS: %.1f", getFps());
    strPut(0, 20, s);
}

// 逻辑
void FrameFun() {
    // 鼠标
    ExMsg msg;
    while(messagePeek(&msg, EX_MOUSE)) {
        if(msg.message == WM_MOUSEMOVE) {
            disturb(msg.x, msg.y, 3, 256);
            stop_flag = 0;
        } else if(msg.message == WM_LBUTTONDOWN) {
            disturb(msg.x, msg.y, 3, 2560);
            stop_flag = 0;
        } else if(msg.message == WM_RBUTTONDOWN) {
            batchDrawEnd();	// 结束双缓冲
            imageFree(src_img);
            imageFree(dest_img);
            free(buf);
            free(buf2);
            graphClose();	// 关闭图形窗口
            exit(0);		// 退出程序
        }
    }
}